﻿using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Documents;
using System.Windows.Input;
using Avaxo.Scripting;

namespace Avaxo.Console
{
    public class Console : Editor
    {
        private readonly CommandBuffer commandBuffer;
        private readonly CommandHistory commandHistory;
        private int currentPromptLength;
        private readonly HashSet<string> languageCache = new HashSet<string>( );

        private TextPointer PromptEnd
        {
            get
            {
                LogicalDirection forward = LogicalDirection.Forward;
                TextPointer start = CaretPosition.GetLineStartPosition( 0 );
                return start.GetNextContextPosition( forward ).GetNextContextPosition( forward );
            }
        }

        public override ILanguageEngine LanguageEngine
        {
            get { return base.LanguageEngine; }
            set
            {
                base.LanguageEngine = value;

                bool shouldWriteLanguageVersion = !languageCache.Contains( value.LanguageName );
                if ( shouldWriteLanguageVersion )
                {
                    languageCache.Add( value.LanguageName );
                }

                if ( commandBuffer != null )
                {
                    commandBuffer.Engine = LanguageEngine;
                    if ( shouldWriteLanguageVersion )
                    {
                        WriteVersion();
                    }
                    WritePrompt();
                }
            }
        }

        public Console()
        {
            commandHistory = new CommandHistory();
            commandBuffer = new CommandBuffer( LanguageEngine );
            Document.Blocks.Clear();
            WriteLine( "Bookvar console.", TextType.Header );
            WriteVersion();
        }

        protected override void OnPreviewKeyDown( KeyEventArgs e )
        {
            if ( e.Key == Key.Back || e.Key == Key.Left )
            {
                EnsurePrompt( e );
            }
            else if ( e.Key == Key.Up )
            {
                if ( !MembersPopup.IsOpen )
                {
                    //if 1 row richtextbox uses this for moving caret to left
                    if ( Document.Blocks.Count == 1 )
                    {
                        EnsurePrompt( e );
                    }
                    if ( !e.Handled )
                    {
                        ClearCurrentLine();
                        string command = commandHistory.PreviousEntry();
                        Write( command, TextType.Default );
                        e.Handled = true;
                    }
                }
            }
            else if ( e.Key == Key.Down )
            {
                if ( !MembersPopup.IsOpen )
                {
                    //if 1 row richtextbox uses this for moving caret to left
                    if ( Document.Blocks.Count == 1 )
                    {
                        EnsurePrompt( e );
                    }
                    if ( !e.Handled )
                    {
                        ClearCurrentLine();
                        string command = commandHistory.NextEntry();
                        Write( command, TextType.Default );
                        e.Handled = true;
                    }
                }
            }
            else if ( e.Key == Key.Enter )
            {
                if ( !e.Handled )
                {
                    bool forceExecution = e.SystemKey == Key.LeftCtrl || e.SystemKey == Key.RightCtrl;

                    HandleInput( forceExecution );
                    if ( !forceExecution )
                    {
                        WritePrompt();
                    }
                    e.Handled = true;
                }
            }
            base.OnPreviewKeyDown( e );
        }

        private void EnsurePrompt( RoutedEventArgs eventArgs )
        {
            TextPointer start = CaretPosition.GetLineStartPosition( 0 );
            TextRange range = new TextRange( start, PromptEnd );

            if ( range.Contains( CaretPosition ) )
            {
                eventArgs.Handled = true;
            }
        }

        private void HandleInput( bool forceExecute )
        {
            string[] lines = GetLines();
            if ( lines.Length == 1 )
            {
                HandleSingleLine( lines[ 0 ] );
            }
            else
            {
                HandleMultipleLines( lines, forceExecute );
            }
        }

        private void HandleSingleLine( string line )
        {
            if ( !string.IsNullOrEmpty( line ) )
            {
                commandHistory.AddEntry( line );
                try
                {
                    object result = commandBuffer.Add( line );
                    if ( result != null )
                    {
                        LanguageEngine.CurrentModule.SetVariable( "_", result );
                        WriteLine( result.ToString(), TextType.Output );
                    }
                }
                catch ( Exception ex )
                {
                    HandleException( ex );
                }
            }
        }

        private void HandleMultipleLines( string[] lines, bool forceExecute )
        {
            string code = string.Join( Environment.NewLine, lines );
            if ( forceExecute )
            {
                commandHistory.AddEntry( code );
                try
                {
                    commandBuffer.Add( code );
                }
                catch ( Exception ex )
                {
                    HandleException( ex );
                    throw;
                }
            }
        }

        private string[] GetLines()
        {
            TextPointer start = CaretPosition.GetLineStartPosition( 0 );
            TextPointer end = CaretPosition.GetNextContextPosition( LogicalDirection.Forward );
            start = start.GetPositionAtOffset( currentPromptLength, LogicalDirection.Forward );

            string line = new TextRange( start, end ).Text;
            return new string[] { line.Trim() };

            //todo: handle multiple lines
            //return Text.Split( new string[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries );
        }

        private void ClearCurrentLine()
        {
            TextRange range = new TextRange( PromptEnd, Document.ContentEnd );
            range.Text = string.Empty;
        }

        private void WritePrompt()
        {
            string prompt = LanguageEngine.LanguageShortName + ">> ";
            currentPromptLength = prompt.Length;
            WriteLine( prompt, TextType.Prompt );
            //set default color
            Write( string.Empty, TextType.Input );
        }

        private void WriteVersion()
        {
            WriteLine( LanguageEngine.ScriptEngine.VersionString, TextType.Header );
            WriteLine( LanguageEngine.ScriptEngine.Copyright, TextType.Header );
        }
    }
}